package com.itbaizhan.paymentdemo.service.impl;
import com.alibaba.fastjson.JSON;
import com.itbaizhan.paymentdemo.config.WxPayConfig;
import com.itbaizhan.paymentdemo.entity.OrderInfo;
import com.itbaizhan.paymentdemo.entity.RefundInfo;
import com.itbaizhan.paymentdemo.enums.OrderStatus;
import com.itbaizhan.paymentdemo.enums.wx.WxNotifyType;
import com.itbaizhan.paymentdemo.enums.wx.WxApiType;
import com.itbaizhan.paymentdemo.enums.wx.WxTradeState;
import com.itbaizhan.paymentdemo.service.IOrderInfoService;
import com.itbaizhan.paymentdemo.service.IPaymentInfoService;
import com.itbaizhan.paymentdemo.service.IRefundInfoService;
import com.itbaizhan.paymentdemo.service.IWxPaymentService;
import com.itbaizhan.paymentdemo.utils.WxVerifierUtils;
import com.itbaizhan.paymentdemo.vo.BaseResult;
import com.itbaizhan.paymentdemo.vo.CodeEnum;
import com.itbaizhan.paymentdemo.vo.PayInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class WxPaymentServiceImpl implements IWxPaymentService {

    @Autowired
    private IOrderInfoService iOrderInfoService;  //订单接口

    @Autowired
    WxPayConfig wxPayConfig;//微信配置参数

    //在WxPayConfig.java 中 得到了CloseableHttpClient ，所以直接注入就可以用
    @Autowired
    CloseableHttpClient wxPayClient;

    @Autowired
    IPaymentInfoService iPaymentInfoService; //交易记录接口

    @Autowired
    IRefundInfoService iRefundInfoService; //退款

    //独占锁
    private  final ReentrantLock lock = new ReentrantLock();

/*native 下单
* 首先要确定订单是否存在，所以要先查询订单，再下单*/
    @Transactional(rollbackFor = Exception.class) //添加事务，如果有异常就回滚
    @Override
    public BaseResult nativePay(String orderNo) throws IOException {
       // 先查询订单,判断是否存在
        OrderInfo orderInfo = iOrderInfoService.findByOrderNo(orderNo);

            if(orderInfo == null){
                return BaseResult.error(CodeEnum.ORDER_ERROR);
            }

            //防止重复下单  订单不是空与二维码也不是空，则返回原先的二维码
        if(orderInfo != null && !StringUtils.hasText(orderInfo.getCodeUrl())){
            PayInfoVo payInfoVo = new PayInfoVo();
            payInfoVo.setCodeUrl(orderInfo.getCodeUrl());
            payInfoVo.setOrderNo(orderInfo.getOrderNo());
            return BaseResult.ok(payInfoVo);
        }

            //调用统一下载api，https://api.mch.weixin.qq.com/v3/pay/transactions/native
        //拼接网址  https://api.mch.weixin.qq.com   +  v3/pay/transactions/native
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));

        //组装请求参数
        HashMap<String,Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", wxPayConfig.getAppid());// 应用id
        paramsMap.put("mchid", wxPayConfig.getMchId());// 商户id
        paramsMap.put("description", "test");// 商品描述
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());// 订单编号
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));// 通知地址
        HashMap<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", orderInfo.getTotalFee());
        paramsMap.put("amount", amountMap);// 订单金额


        //4将参数转为json字符串
        /*<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>*/
        String string = JSON.toJSONString(paramsMap);
        log.info("******Native下单参数======{}"+string);


        //5准备消息的body   微信那里是接收json格式
        StringEntity entity = new StringEntity(string,"UTF-8");
        entity.setContentType("application/json");
     //6准备请求头
        httpPost.setHeader("Accept","application/json");

      //7,完成签名并执行请求  向微信发送请求，从微信返回的请求数据
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try{
            //8,拿出body响应  响应体
            String bodyString = EntityUtils.toString(response.getEntity());

            //9 获取响应代码
            int statusCode = response.getStatusLine().getStatusCode();

            //10 判断响应代码
            //请求成功后，返回的是json数据，转换为object类型
            if(statusCode == 200){
                HashMap<String,String> responseMap = JSON.parseObject(bodyString, HashMap.class);
                //11 取出code_url  ,返回前端
                String codeUrl = responseMap.get("code_url");
               //更新code_url  ，//用于防止重复下单
                iOrderInfoService.saveCodeUrl(orderInfo.getId(),codeUrl);

                PayInfoVo payInfoVo = new PayInfoVo();
                payInfoVo.setCodeUrl(codeUrl);
                payInfoVo.setOrderNo(orderInfo.getOrderNo());
                return BaseResult.ok(payInfoVo);
            }else {
                log.error("Native 下单失败响应码" + statusCode + "返回结果" + bodyString);
                return BaseResult.error(CodeEnum.PAYMENT_ERROR);
            }

        }finally {
            response.close();
        }



    }

    /*修改订单状态
    * 从bodyMap中获取订单编号*/
    @Override
    public void updateOrderStatus(HashMap<String, Object> bodyMap) throws GeneralSecurityException {

        log.info("************修改订单状态************");

        //获取明文   微信发送回来的是密文，要先解密
        String plainText = WxVerifierUtils.decrypt(bodyMap, wxPayConfig.getApiV3Key());
        //2,字符串转为map
        HashMap plainTextMap = JSON.parseObject(plainText, HashMap.class);
        //3,获取订单编号
        String orderNo = (String) plainTextMap.get("out_trade_no");

        //尝试获取得锁，如果成功返回true  获取失败立即返回false 不必一直等待锁的释放
        if(lock.tryLock()){

            try{
                //4,根据订单编号获取订单信息
                OrderInfo orderInfo = iOrderInfoService.findByOrderNo(orderNo);
                //5,判定订单状态   如果订单状态不是未支付状态，则返回
                if(!OrderStatus.NOTPAY.getType().equals(orderInfo.getOrderStatus())){
                    return;
                }

                //6更新状态
                iOrderInfoService.updateOrderStatus(orderInfo.getId(),OrderStatus.SUCCESS);


                //添加交易记录
                iPaymentInfoService.createPaymentInfo(plainTextMap);

            }finally {
                //手动释放锁
                lock.unlock();
            }

        }



    }


    @Override
    public String queryOrder(String orderNo) throws IOException {
        log.info("**********查询订单**********");


        //组装请求url
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),orderNo);
       url =  wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
        //2 ,获取httpget
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept","application/json");

        //3完成签名后发起请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);

        try {
            //4,获取响应体body
            String bodyString = EntityUtils.toString(response.getEntity());
            //5,获取状态码
            int statusCode = response.getStatusLine().getStatusCode();
            //6.判断状态码
            if (statusCode == 200) {
                log.info("查询成功 =>{}", bodyString);
            } else if (statusCode == 204) {
                log.info("查询成功，没有body返回");
            } else {
                log.info("查询订单接口失败" + statusCode + "返回结果" + bodyString);
                throw new IOException("request failed");
            }
            return bodyString;
        }finally {
            response.close();
        }
    }

    /*检查订单状态
    * 检查在微信上的订单状态是否更新了,本地的数据与微信的可能未有同步*/
    @Override
    public void checkOrderStatus(String orderNo) throws IOException {

        log.info("*********根据订单编号核实订单状态");

        //1，上面的方法就是根据订单编号查询订单信息，
        String result = this.queryOrder(orderNo);
        //2，JSON转为Map
        HashMap resultMap = JSON.parseObject(result, HashMap.class);
        //3，获取微信订单状态  key值在微信官网上查找
        String tradeState = (String) resultMap.get("trade_state");
        //4，判断订单状态
        if(tradeState.equals(WxTradeState.SUCCESS.getType())){
            log.info("********核实订单已支付===>{}",orderNo);
            //5,如果确认订单已支付,则更新订单状态
            iOrderInfoService.updateOrderStatus(orderNo,OrderStatus.SUCCESS);
            //6记录支付日志
            iPaymentInfoService.createPaymentInfo(resultMap);
        }
        //关闭单
        if(tradeState.equals(WxTradeState.NOTPAY.getType())){

            this.cancelOrderNo(orderNo);
        }
    }

    /**
     * 关闭订单
     * @param orderNo
     */
    @Override
    public void cancelOrderNo(String orderNo) throws IOException {
        //调用微信api关闭订单
        this.closeOrder(orderNo);
        //更新商户端的订单状态
        iOrderInfoService.updateOrderStatus(orderNo,OrderStatus.CANCEL);
    }

    /**
     * 根据订单编号修改微信后台的订单状态
     * @param orderNo
     */
    private void closeOrder(String orderNo) throws IOException {
        log.info("*********关单接口******");
        //组装请求url
        String url = String.format(wxPayConfig.getDomain().concat(WxApiType.CLOSE_ORDER_BY_NO.getType()),orderNo);

        //2 ,获取httppost
        HttpPost httpPost = new HttpPost(url);

        //3,组装参数
        HashMap<String,Object> paramsMap = new HashMap<>();
        paramsMap.put("mchid",wxPayConfig.getMchId());

        //4将参数转为json字符串
        /*<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>*/
        String string = JSON.toJSONString(paramsMap);


        //5准备消息的body   微信那里是接收json格式
        StringEntity entity = new StringEntity(string,"UTF-8");
        entity.setContentType("application/json");
        //6准备请求头
        httpPost.setHeader("Accept","application/json");

        //7,完成签名并执行请求  向微信发送请求，从微信返回的请求数据
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        //8获取状态码
        try{
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200){
                log.info("关单成功");
            }else if (statusCode == 204){
                log.info("关单成功,没有数据返回");
            }else {
                log.info("关单失败，响应码 =" + statusCode);
                throw new IOException("request failed");
            }
        }finally {

            response.close();
        }
    }

    //退款

    /**
     *
     * @param orderNo  退款编号
     * @param reason  退款理由
     * @Transactional   添加事务，因为执行了 创建退款单与更新订单状态，两者都成功执行，或者都失败
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo, String reason) throws IOException {

        log.info("*****创建退款");
        //根据订单编号创建退款单

        RefundInfo refundInfo = iRefundInfoService.createRefundByOrderNo(orderNo, reason);
        //System.out.println(refundByOrderNo);

        log.info("********正在调用微信支付申请退款API**********");
        //2调用申请退款

        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType()));

        //组装请求参数  上官网查看所需要的参数
        HashMap<String,Object> paramsMap = new HashMap<>();
        paramsMap.put("out_trade_no", refundInfo.getOrderNo());// 订单编号
        paramsMap.put("out_refund_no", refundInfo.getRefundNo());// 商户退款单号
        paramsMap.put("reason",reason); //退款原因
        paramsMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款请求地址
        HashMap<String, Object> amountMap = new HashMap<>();
        amountMap.put("refund", refundInfo.getRefund());//退款金额
        amountMap.put("total", refundInfo.getTotalFee());//原始订单金额
        amountMap.put("currency", "CNY");//退款金额
        paramsMap.put("amount", amountMap);// 订单金额


        //4将参数转为json字符串
        /*<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>*/
        String string = JSON.toJSONString(paramsMap);
        log.info("******申请退款参数======{}"+string);


        //5准备消息的body   从微信那里是接收json格式
        StringEntity entity = new StringEntity(string,"UTF-8");
        entity.setContentType("application/json");
        //6准备请求头
        httpPost.setHeader("Accept","application/json");

        //7,完成签名并执行请求  向微信发送请求，从微信返回的请求数据
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try{
            //8,获取响应体body
            String bodyString = EntityUtils.toString(response.getEntity());
            //9,获取状态码
            int statusCode = response.getStatusLine().getStatusCode();
            //10.判断状态码
            if (statusCode == 200) {
                log.info("查询成功 =>{}", bodyString);
            } else if (statusCode == 204) {
                log.info("查询成功，没有body返回");
            } else {
                log.info("查询订单接口失败" + statusCode + "返回结果" + bodyString);
                throw new IOException("request failed");
            }
            //11,更新状态 支付成功 ---》退款中
            iOrderInfoService.updateOrderStatus(refundInfo.getOrderNo(),OrderStatus.REFUND_PROCESSING);
            //更新退款单
            iRefundInfoService.updateRefund(bodyString);
        }finally {
            response.close();
        }

    }

    /**
     * 处理退款
     * @param resultMap  是从controller 退款的请求中
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processRefunds(Map<String, Object> resultMap) throws GeneralSecurityException {
        log.info("**********处理退款单***********");

        //因为与修改订单返回的数据是一样的，所以与修改订单的解密是一样的
        //获取明文   微信发送回来的是密文，要先解密
        String plainText = WxVerifierUtils.decrypt(resultMap, wxPayConfig.getApiV3Key());
        //2,字符串转为map
        HashMap plainTextMap = JSON.parseObject(plainText, HashMap.class);
        //3,获取订单编号
        String orderNo = (String) plainTextMap.get("out_trade_no");

        if(lock.tryLock()){
            try{
                //4根据订单编号查询订单信息
                OrderInfo byOrderNo = iOrderInfoService.findByOrderNo(orderNo);

                //5判断是否已经更新过  如果不是退款中，则返回
                if(!OrderStatus.REFUND_PROCESSING.getType().equals(byOrderNo.getOrderStatus())){
                    return;
                }

                //6更新订单状态
                iOrderInfoService.updateOrderStatus(byOrderNo.getId(),OrderStatus.REFUND_SUCCESS);

                //7更新退款单
                iRefundInfoService.updateRefund(plainText);

            }finally {
                lock.unlock();//主动释放锁
            }
        }

    }
}